module test_queue
    use testdrive, only: new_unittest, unittest_type, error_type, check
    use queue_m, only: queue_t
    implicit none
    private

    public :: collect_queue

contains

    !> Collect all exported unit tests
    subroutine collect_queue(testsuite)
        !> Collection of tests
        type(unittest_type), allocatable, intent(out) :: testsuite(:)

        testsuite = [ &
                    new_unittest("enqueue", test_enqueue), &
                    new_unittest("dequeue", test_dequeue), &
                    new_unittest("size", test_size), &
                    new_unittest("clear", test_clear) &
                    ]

    end subroutine collect_queue

    subroutine test_enqueue(error)
        type(error_type), allocatable, intent(out) :: error
        type(queue_t) :: queue
        class(*), allocatable :: data
        call queue%enqueue(1)
        call queue%enqueue(10)
        call queue%enqueue(3.5)
        call queue%enqueue(cmplx(1, 2))
        call check(error, queue%size(), 4, "size")
        if (allocated(error)) return

        call queue%dequeue(data)
        call check(error, to_int(data), 1, "dequeue 1")
        if (allocated(error)) return

        call queue%dequeue(data)
        call check(error, to_int(data), 10, "dequeue 2")
        if (allocated(error)) return
        call check(error, queue%size(), 2, "size")

        call queue%clear()
        call check(error, queue%size(), 0, "clear")

    end subroutine test_enqueue

    subroutine test_dequeue(error)
        type(error_type), allocatable, intent(out) :: error
        type(queue_t) :: queue
        class(*), allocatable :: data
        call queue%enqueue(1)
        call queue%dequeue()
        call check(error, queue%size(), 0, "size")
        if (allocated(error)) return

        call queue%enqueue(10)
        call queue%dequeue(data)
        call check(error, to_int(data), 10, "dequeue 1")
        if (allocated(error)) return

        call queue%enqueue(3.5)
        call queue%enqueue(-1)
        call queue%dequeue(data)
        call queue%dequeue(data)
        call check(error, to_int(data), -1, "dequeue 2")
        if (allocated(error)) return
        call check(error, queue%size(), 0, "size")
        
    end subroutine test_dequeue

    subroutine test_size(error)
        type(error_type), allocatable, intent(out) :: error
        type(queue_t) :: queue
        call queue%enqueue(1)
        call check(error, queue%size(), 1, "size 1")
        if (allocated(error)) return
        
        call queue%dequeue()
        call check(error, queue%size(), 0, "size 2")
        if (allocated(error)) return
        
        call queue%clear()
        call check(error, queue%size(), 0, "size 3")
    end subroutine test_size

    subroutine test_clear(error)
        type(error_type), allocatable, intent(out) :: error
        type(queue_t) :: queue
        call queue%enqueue(1)
        call queue%clear()
        call check(error, queue%size(), 0, "clear")
        if (allocated(error)) return
        
        call queue%clear()
        call check(error, queue%size(), 0, "clear 2")
    end subroutine test_clear

    function to_int(in) result(out)
        class(*), intent(in) :: in
        integer :: out
        select type (in)
        type is (integer)
            out = in
        class default
            out = 0
        end select
    end function to_int

end module test_queue
